/* [n_msi]
 *********************
 * Things to remember:
 *
 * - detail is threadsafe, use caution, all changes will be seen by all threads; recommend: only use to .get data never .set
 * - global variables are global to all threads, use load & unload to initialize & free up memory
 * - use global isInit to initialize global variables requiring detail to be setup
 * - when using global isInit, the code must be mutex protected so all threads do not run the init, just one
 * - inter-thread coordination can be coordinated via the provided mutex
 */
#include <process.h>
#include <stdio.h>

#define DLL_EXPORT
#include "start.h"
#include "Database.h"
#include "Exec.h"
#include "NetworkManager.h"

#ifdef _WIN32
#ifdef _DEBUG
	#define _CRTDBG_MAP_ALLOC
	#include <stdlib.h>
	#include <crtdbg.h>
	#undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
	#define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
	#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

#include <iostream>
using namespace std;

#ifdef __cplusplus
extern "C" {
#endif	/*__cplusplus*/


// default global variables within dll
ParamManager *supported = NULL;
// module-specific global variables within dll
bool isInit = false;
Database *db = NULL;
ParamManager *exceptions = NULL;


// called after the module is loaded, useful to load global variables; Returns supported actions as a ParamManager*
DECLDIR ParamManager* load(void) {
	supported = new ParamManager();
	supported->setOverwrite(false);
	supported->set("action", "msi");

	return supported;
}


// called just before the module is unloaded, useful to unload global variables
DECLDIR void unload(void) {
	if(supported) {
		delete supported;
	}
	if(db) {
		delete db;
	}
	if(exceptions) {
		delete exceptions;
	}
}


bool restoreVM(ParamManager *tdata, HANDLE mutex);
char* dec2base(char *_orig, char *_base, char *_length);


void init(HANDLE mutex, ParamManager &detail) {
	MYSQL_RES *rUpdate = NULL;

	// ensure we only init once
	if(!isInit) {
		char query[DEFAULT_BUFLEN];

		// Instance Table
		db = new Database(
					detail.get("db_user"), detail.get("db_pass"),
					detail.get("db_name"),
					detail.get("db_host"), atoi(detail.get("db_port")));

		// re-establish mysql connection if lost
		bool connect = true;
		mysql_options(&(db->conn), MYSQL_OPT_RECONNECT, &connect);

		sprintf_s(query, DEFAULT_BUFLEN, "CREATE TABLE IF NOT EXISTS n_msi_instance (");
		strcat_s(query, DEFAULT_BUFLEN, "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY");
		strcat_s(query, DEFAULT_BUFLEN, ", state INT DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ", num_installs INT DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ", num_packages INT DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ", track_id INT DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ")");

		// init is wrapped by mutex, allowing safe mysql call here
		mysql_query(&(db->conn), query);
		rUpdate = mysql_store_result(&(db->conn));
		// free up the result from the mysql query
		mysql_free_result(rUpdate);

		sprintf_s(query, DEFAULT_BUFLEN, "CREATE TABLE IF NOT EXISTS n_msi_targetpc (");
		strcat_s(query, DEFAULT_BUFLEN, "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY");
		strcat_s(query, DEFAULT_BUFLEN, ", netbios VARCHAR(256)");
		strcat_s(query, DEFAULT_BUFLEN, ", isAvailable BOOL DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ", isReset BOOL DEFAULT 0");
//		strcat_s(query, DEFAULT_BUFLEN, ", isReady BOOL DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ", vmIndex INT DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ", snapshotId INT DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ", inUseBy VARCHAR(256)");
		strcat_s(query, DEFAULT_BUFLEN, ", tsCheckout TIMESTAMP");
		strcat_s(query, DEFAULT_BUFLEN, ", isIgnore BOOL DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ")");

		// init is wrapped by mutex, allowing safe mysql call here
		mysql_query(&(db->conn), query);
		rUpdate = mysql_store_result(&(db->conn));
		// free up the result from the mysql query
		mysql_free_result(rUpdate);

		// Software packages to test with
		sprintf_s(query, DEFAULT_BUFLEN, "CREATE TABLE IF NOT EXISTS n_msi_package (");
		strcat_s(query, DEFAULT_BUFLEN, "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY");
		strcat_s(query, DEFAULT_BUFLEN, ", package VARCHAR(256)");
		strcat_s(query, DEFAULT_BUFLEN, ", version VARCHAR(25)");
		strcat_s(query, DEFAULT_BUFLEN, ", isIgnore BOOL DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ")");

		// init is wrapped by mutex, allowing safe mysql call here
		mysql_query(&(db->conn), query);
		rUpdate = mysql_store_result(&(db->conn));
		// free up the result from the mysql query
		mysql_free_result(rUpdate);

		// loop through each available vm and restore

		isInit = true;
	}
}


DECLDIR void start(HANDLE mutex, ParamManager &detail, char *index) {
	MYSQL_RES *rSelect, *rUpdate;
	MYSQL_ROW row;
	char query[DEFAULT_BUFLEN];
	ParamManager tdata;		// Use this instead of detail to write data (ParamManger is not threadsafe, and all thread see this.)

	// mutex protected init sequence
	WaitForSingleObject(mutex, INFINITE);	// Request ownership of mutex
	init(mutex, detail);					// Init sequence
	ReleaseMutex(mutex);					// Release mutex for other threads
	

	// check cmd for action to take
	if(!strcmp(detail.get("action"), "add")) {
		// Should be specified:
		// detail.get("instance_id")
		// detail.get("num_installs")
		// detail.get("num_packages")
		// detail.get("length")

		// count the number of addm programs to test
		sprintf(query, "SELECT id FROM n_msi_package WHERE isIgnore = 0");

		// thread-safe mysql call
		rSelect = db->threadsafe_query(mutex, query);
		int num_rows = mysql_num_rows(rSelect);
		// free up the result from the mysql query
		db->safe_mysql_free_result(rSelect);


		// Insert a new instance
		sprintf(query, "INSERT INTO n_msi_instance (state, num_installs, num_packages, track_id) VALUES (0, \"%s\", %d, 1)",
					detail.get("length"), num_rows);

		// -- Thread-Safe Mysql call --
		// Request ownership of mutex
		WaitForSingleObject(mutex, INFINITE);
		// Make mysql calls which need Mutex protection
		mysql_query(&(db->conn), query);
		printf("msi_instance: '%s'\n", query);
		rUpdate = NULL;
		rUpdate = mysql_store_result(&(db->conn));
		// obtain insert id
		int insert_id = mysql_insert_id(&(db->conn));
		// Release the mutex
		ReleaseMutex(mutex);
		// free up the result from the mysql query
		if(rUpdate != NULL) {
			mysql_free_result(rUpdate);
		}

		// Create a new sequence table for this new instance
		sprintf(query, "CREATE TABLE IF NOT EXISTS n_msi_sequence_%d (", insert_id);
		strcat(query, "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY");
		strcat(query, ", pattern VARCHAR(27)");
		strcat(query, ", num_stuck INT DEFAULT 0");
		strcat(query, ", isIgnore BOOL DEFAULT 0");
		strcat(query, ", track_id INT DEFAULT 0");
		strcat(query, ")");
		printf("msi_sequence: '%s'\n", query);

		// thread-safe mysql call
		rUpdate = db->threadsafe_query(mutex, query);
		// free up the result from the mysql query
		db->safe_mysql_free_result(rUpdate);

		// Insert into sequence table, records representing all the tests to complete.
		printf("num_rows = %f\n", num_rows);
		printf("length = %s\n", detail.get("length"));
		printf("pow(#, #) = %f\n", pow((double)num_rows, (double)atoi(detail.get("length"))));
		double num_records = pow((double)num_rows, (double)atoi(detail.get("length")));
		for(double loop = 0; loop < num_records; loop ++) {
			sprintf(query, "INSERT INTO n_msi_sequence_%d (num_stuck, track_id) VALUES (0, 0)",
						insert_id);
			// thread-safe mysql call
			rSelect = db->threadsafe_query(mutex, query);
			// free up the result from the mysql query
			db->safe_mysql_free_result(rSelect);
		}
	}
	else if(!strcmp(detail.get("action"), "start")) {
		printf("start\n");
		// Should be specified:
		// detail.get("db_host");
		// detail.get("instance_id")


		// for now we obtain the instance id automatically by looking up the first one, this could be passed in
		sprintf(query, "SELECT id FROM n_msi_instance WHERE state != 2");

		// thread-safe mysql call
		char *instance_id = NULL;
		rSelect = db->threadsafe_query(mutex, query);
		if(row = mysql_fetch_row(rSelect)) {
			instance_id = new char[strlen(row[0]) + 1];
			strcpy(instance_id, row[0]);
			printf("instance_id = %s\n", instance_id);\
		}
		else {
			printf("select failed\n");
			return;
		}
		// free up the result from the mysql query
		db->safe_mysql_free_result(rSelect);



		// increases the track_id
		sprintf(query, "UPDATE n_msi_instance SET track_id=track_id+1 WHERE id=\"%s\"", instance_id);

		// thread-safe mysql call
		rSelect = db->threadsafe_query(mutex, query);
		// free up the result from the mysql query
		db->safe_mysql_free_result(rSelect);
		printf("track_id increased\n");
		

		// Count the number of rows in the sequence
		sprintf(query, "SELECT id FROM n_msi_sequence_%s WHERE isIgnore=0", instance_id);

		// thread-safe mysql call
		rSelect = db->threadsafe_query(mutex, query);
		int num_rows = mysql_num_rows(rSelect);
		// free up the result from the mysql query
		db->safe_mysql_free_result(rSelect);
		printf("gathered sequence_%s\n", instance_id);
		

		// Now generate a new task with the provided details plus this todo_count
		NetworkManager nm;
		Connection *conn = nm.add();
		conn->settings.set("host", detail.get("db_host"));
		conn->settings.set("port", "2244");
		nm.start(conn);

		printf("sending task_add\n");
		conn->write("cmd=task_add\n");

		ParamManager add;
		while(true) {
			// wait forever until network activity
			nm.waitForReady();

			// process the network activity
			if(nm.isData()) {
				while(conn = nm.getDataConnection()) {
					printf("recv: '%s'\n", conn->bm->getNext());
					if(strstr(conn->bm->getNext(), "cmd=task_add_reply")) {
						add.init(conn->bm->getNext(), "|");
						conn->bm->pop();
						break;
					}
					conn->bm->pop();
				}
				break;
			}
		}

		char data[DEFAULT_BUFLEN];
		int task_id = atoi(add.get("id"));
		sprintf(data, "cmd=task_set|id=%d|data=todo_count|value=%d\n", task_id, num_rows);						conn->write(data);

		sprintf(data, "cmd=task_set|id=%d|data=block_size|value=%s\n", task_id, "100");							conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=timeout|value=%s\n", task_id, detail.get("timeout"));			conn->write(data);

		sprintf(data, "cmd=task_set|id=%d|data=db_user|value=%s\n", task_id, detail.get("db_user"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_pass|value=%s\n", task_id, detail.get("db_pass"));			conn->write(data);
//		Sleep(3000);
		sprintf(data, "cmd=task_set|id=%d|data=db_name|value=%s\n", task_id, detail.get("db_name"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_host|value=%s\n", task_id, detail.get("db_host"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_port|value=%s\n", task_id, detail.get("db_port"));			conn->write(data);

		sprintf(data, "cmd=task_set|id=%d|data=todo|value=msi\n", task_id);										conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=action|value=process\n", task_id);								conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=instance_id|value=%s\n", task_id, instance_id);					conn->write(data);
//		sprintf(data, "cmd=task_set|id=%d|data=num_installs|value=%d\n", task_id, 3);							conn->write(data);
//		sprintf(data, "cmd=task_set|id=%d|data=num_packages|value=%d\n", task_id, 3);							conn->write(data);
		sprintf(data, "cmd=task_start|id=%d\n", task_id);														conn->write(data);

		delete[] instance_id;
	}
	else if(!strcmp(detail.get("action"), "process")) {
		// Should be specified:
		// detail.get("instance_id")
		// detail.get("num_installs")
		// detail.get("num_packages")
		printf("msi::process - %s\n", index);

		// Query the specified instance
		sprintf(query, "SELECT state, num_installs, num_packages, track_id FROM n_msi_instance WHERE id=%s", detail.get("instance_id"));
		printf("query = '%s'\n", query);

		// thread-safe mysql call
		rSelect = db->threadsafe_query(mutex, query);
		// obtain result
		if(row = mysql_fetch_row(rSelect)) {
			tdata.set("state", row[0]);
			tdata.set("num_installs", row[1]);
			tdata.set("num_packages", row[2]);			// The state variable, and these two could have been set in the 'start' section
			tdata.set("track_id", row[3]);
		}
		else {
			printf("instance select failed\n");
			return;
		}
		// free up the result from the mysql query
		db->safe_mysql_free_result(rSelect);


		// Work with State variable
		if(!strcmp(tdata.get("state"), "2")) {
			return;
		}
		else if(!strcmp(tdata.get("state"), "0")) {
			// adjust state to '1' processing
			sprintf(query, "UPDATE n_msi_instance SET state=1 WHERE id=\"%s\"", detail.get("instance_id"));
			// thread-safe mysql call
			rSelect = db->threadsafe_query(mutex, query);
			// free up the result from the mysql query
			db->safe_mysql_free_result(rSelect);

			tdata.set("state", "1");
		}


		// + 1 to the index
		BIGNUM *tmp = BN_new();
		BN_dec2bn(&tmp, index);
		BN_add_word(tmp, 1);	// increase our id by one, as db tables start at 1 not 0
		char *sindex = BN_bn2dec(tmp);
		BN_free(tmp);


		// Generate the permutation
		char chars[27];	strcpy(chars, "abcdefghijklmnopqrstuvwxyz");
		chars[ atoi(tdata.get("num_packages")) ] = 0;
		char num_chars[DEFAULT_BUFLEN];
		_itoa(strlen(chars), num_chars, 10);
		char *output = dec2base(index, num_chars, tdata.get("num_installs"));
		char *teststr = new char[atoi(tdata.get("num_installs")) + 1];
		memset(teststr, 0, atoi(tdata.get("num_installs")) + 1);
		for(int loop = 0; loop < atoi(tdata.get("num_installs")); loop ++) {
			teststr[loop] = chars[output[loop]];
		}
		_strrev(teststr);
		printf("teststr: '%s'\n", teststr);


		// Save permutation to record
		sprintf(query, "UPDATE n_msi_sequence_%s SET pattern=\"%s\" WHERE id=%s",
			detail.get("instance_id"), teststr, sindex);
		// thread-safe mysql call
		rSelect = db->threadsafe_query(mutex, query);
		// free up the result from the mysql query
		db->safe_mysql_free_result(rSelect);


		// Abort if the permutation has a repeating character (a work around if the permutation algorithm cannot be altered);
		int found = 0;
		for(int ctr = 0; ctr < strlen(chars); ctr ++) {
			if(strchr(teststr, chars[ctr])) {
				found ++;
			}
		}
		if(found != atoi(tdata.get("num_installs"))) {
			printf("abort, repeating char\n");

			// update the track_id
			sprintf(query, "UPDATE n_msi_sequence_%s SET track_id=%s WHERE id=%s",
					detail.get("instance_id"), tdata.get("track_id"), sindex);
			// thread-safe mysql call
			rSelect = db->threadsafe_query(mutex, query);
			// free up the result from the mysql query
			db->safe_mysql_free_result(rSelect);

			// mark record to ignore this sequence in the future
			sprintf(query, "UPDATE n_msi_sequence_%s SET isIgnore=1 WHERE id=%s",
				detail.get("instance_id"), sindex);
			// thread-safe mysql call
			rSelect = db->threadsafe_query(mutex, query);
			// free up the result from the mysql query
			db->safe_mysql_free_result(rSelect);

			// Clean up allocated memory
			OPENSSL_free(sindex);
			delete[] teststr;
			delete[] output;

			// free up mysql thread-specific memory
			mysql_thread_end();
			return;
		}

		
		// Abort if isIgnore is set or track_id already equals the current track_id, indicating a continuation of testing
		sprintf(query, "SELECT track_id, isIgnore FROM n_msi_sequence_%s WHERE id=%s",
					detail.get("instance_id"), sindex);
		// thread-safe mysql call
		rSelect = db->threadsafe_query(mutex, query);
		strcpy(query, "");
		if(row = mysql_fetch_row(rSelect)) {
			strcpy(query, row[0]);
		}
		// free up the result from the mysql query
		db->safe_mysql_free_result(rSelect);
		if(!strcmp(tdata.get("track_id"), query) || !strcmp(detail.get("isIgnore"), "1")) {
			printf("abort, sequence already tested\n");

			// update the track_id
			sprintf(query, "UPDATE n_msi_sequence_%s SET track_id=%s WHERE id=%s",
					detail.get("instance_id"), tdata.get("track_id"), sindex);
			// thread-safe mysql call
			rSelect = db->threadsafe_query(mutex, query);
			// free up the result from the mysql query
			db->safe_mysql_free_result(rSelect);

			// Clean up allocated memory
			OPENSSL_free(sindex);
			delete[] teststr;
			delete[] output;

			// free up mysql thread-specific memory
			mysql_thread_end();
			return;
		}


		// Obtain a test machine from the available queue to test this sequence, this may require waiting
		// (a queue system would be better than this, but this will work long as there is a test pc for every stbd_t)
		while(true) {
			long num_rows = 0;

			// -- Thread-Safe Mysql call --
			// Request ownership of mutex
			WaitForSingleObject(mutex, INFINITE);

			sprintf(query, "SELECT id, netbios, vmIndex, snapshotId, isReset FROM n_msi_targetpc WHERE isAvailable=1 AND isIgnore=0 LIMIT 1");
			mysql_query(&(db->conn), query);
			MYSQL_RES *result = mysql_store_result(&(db->conn));
			num_rows = mysql_num_rows(result);

			// break out of forever while if we succeeded
			if(num_rows > 0) {
				// we have a result, save the row data
				MYSQL_ROW row = mysql_fetch_row(result);
				tdata.set("targetpcId", row[0]);
				tdata.set("netbios", row[1]);
				tdata.set("vmIndex", row[2]);
				tdata.set("snapshotId", row[3]);
				tdata.set("isReset", row[4]);
				// free up last data query set
				if(result != NULL) {
					mysql_free_result(result);
				}

				// form query to claim the targetpc
				sprintf(query, "UPDATE n_msi_targetpc SET isAvailable=0, inUseBy=\"%s\" WHERE id=\"%s\"", index, tdata.get("targetpcId"));
				// exec sql query to claim the targetpc
				mysql_query(&(db->conn), query);

				// Release the mutex
				ReleaseMutex(mutex);

				break;	// exit forever loop
			}
			else {
				// free up last data query set
				if(result != NULL) {
					mysql_free_result(result);
				}

				// Release the mutex
				ReleaseMutex(mutex);

				// otherwise take a break, sleep 20 seconds
//				printf("waiting for a targetpc\n");
				Sleep(20 * 1000);
			}
		}


		// if the vm is not reset, then reset it now before starting
		if(!atoi(tdata.get("isReset"))) {
			// Restore VM from snapshot
			while(!restoreVM(&tdata, mutex))	{	// if restore fails, just try again
				printf("%s has Lost domain trust?\n", tdata.get("netbios"));
			}
		}
		// flag as no longer reset, because we are about to do work on it
		sprintf(query, "UPDATE n_msi_targetpc SET isReset=0 WHERE id=\"%s\"", tdata.get("targetpcId"));
		db->threadsafe_query(mutex, query);


		// Cycle through installs being performed
		printf("proceeding with installs\n");
		unsigned int loopmax = strlen(teststr);
		for(unsigned int ctr = 0; ctr < loopmax; ctr ++) {
			int addm = teststr[ctr] - 'a' + 1;
			printf("teststr = '%s', ctr = %d\n", teststr, ctr);
			printf("next install index: %d\n", addm);

			// clear the temp directory, so only thing in there will be the msi log from this run
/*			char data[DEFAULT_BUFLEN];
			sprintf(data, "del \\\\%s\\c$\\windows\\temp\\msi*.log /q", tdata.get("netbios"));
			printf("Exec: '%s'\n", data);
			Exec proc3(data);
			while(proc3.getNext());	// wait for command to finish by reading the buffer till its empty
*/
			// count the number of addm programs to test
			sprintf(query, "SELECT package, version FROM n_msi_package WHERE isIgnore = 0");
			// thread-safe mysql call
			rSelect = db->threadsafe_query(mutex, query);
			if(rSelect) {
				// obtain result
				while(row = mysql_fetch_row(rSelect)) {
					if(--addm == 0) {
						break;
					}
				}

				// work-around: save off the addm data, should be able to just use 'row', must be a memory bug that needs to be fixed
				char addm_group[DEFAULT_BUFLEN];
				char addm_version[DEFAULT_BUFLEN];
				sprintf(addm_group, "%s", row[0]);
				sprintf(addm_version, "%s", row[1]);

				// install package
				sprintf(query, "nodes\\n_msi\\ADDMInstaller.exe /W=%s /%s;%s /PRODUCTION /QUIET=N"
						, tdata.get("netbios")
						, row[0]	// package, aka addm group
						, row[1]	// version
						);
				printf("cmd = '%s'\n", query);
				system(query);
				//while(proc.getNext());	// wait for command to finish by reading the buffer till its empty

				// Watch for finish algorithm:
				// - Check every 20 seconds for a finish
				// - If 3 "finishes" in a row, then we believe the install is finished.
				//     this is important as multiple installs may have moments where
				//     the msi is finished and another is being started
				// - If X minutes pass then we treat as an msiexec lockup
//				Sleep(1 * 60 * 1000);	// Sleep for 1 minute
				int count_finished = 0;
				int loops = 0;
				while(true) {
					// Sleep 20 seconds
					Sleep(20 * 1000);
					++ loops;

					// Check for a finish
					sprintf(query, "powershell .\\nodes\\n_msiexec\\getdata.ps1 %s", tdata.get("netbios"));
					Exec check(query);

					// Check return data
					char *ptr = NULL;
					int count = 0;
					while(ptr = check.getNext()) {
						if(strstr(ptr, "msiexec.exe")) {
							count ++;
						}
					}

					if(count == 0) {
						// 2nd technique if powershell script failed, use pslist instead to get count of msiexec processes
						printf("2nd Technique on %s\n", tdata.get("netbios"));
						sprintf(query, "C:\\pstools\\pslist.exe \\\\%s 2>c:\\temp\\%s_stderr.txt",
								tdata.get("netbios"), tdata.get("netbios"));
						Exec proc10(query);
						while(ptr = proc10.getNext()) {
							if(strstr(ptr, "msiexec")) {
								count ++;
							}
						}
					}

					if(count == 1) {
						count_finished ++;
						printf("%s finish noted: %s,%s\n", tdata.get("netbios"), row[0], row[1]);
					}
					else if(count == 2) {
						count_finished = 0;
						printf("%s installing %s,%s: %d seconds\n", tdata.get("netbios"), row[0], row[1], loops * 20);
					}

					if(count_finished == 3) {
						// install is finished, stop waiting
						printf("%s install finished, %s,%s\n", tdata.get("netbios"), row[0], row[1]);

						// make a copy of the extended msi log file for the purpose of comparision to the failed log
						// create a directory with the package name
						sprintf(query, "mkdir \\\\tloyd-ser\\c$\\proj1618\\_success\\%s-%s", addm_group, addm_version);
						Exec proc(query);
						while(proc.getNext());	// wait for command to finish by reading the buffer till its empty
						ULONGLONG timestamp = GetTickCount64();
						sprintf(query, "mkdir \\\\tloyd-ser\\c$\\proj1618\\_success\\%s-%s\\%llu", addm_group, addm_version, timestamp);
						Exec proc5(query);
						while(proc5.getNext());	// wait for command to finish by reading the buffer till its empty

						// save off log file underneath the package name
						sprintf(query, "copy \\\\%s\\c$\\windows\\temp\\Msi*.log \\\\tloyd-ser\\c$\\proj1618\\_success\\%s-%s\\%llu\\ /y",
								tdata.get("netbios"), addm_group, addm_version, timestamp);
						printf("cmd = '%s'\n", query);
						system(query);

						// touch a file in order to save off the vm name
						sprintf(query, "cat > \\\\tloyd-ser\\c$\\proj1618\\_success\\%s-%s\\%llu\\_%s.txt",
								addm_group, addm_version, timestamp, tdata.get("netbios"));
						system(query);

						printf("copy complete\n");
						break;
					}

					// Test for stuck process
					if(loops >= (60 * 3)) {	// Has it been 1 hour?
						printf("Installation is taking too long.  Noting stuck process.\n");

						// Treating as msiexec lockup.  Note in database the stuck process.
						sprintf(query, "UPDATE n_msi_sequence_%s SET num_stuck=num_stuck+1 WHERE id=%s",
								detail.get("instance_id"), sindex);
						// thread-safe mysql call
						rUpdate = NULL;
						rUpdate = db->threadsafe_query(mutex, query);
						// free up the result from the mysql query
						db->safe_mysql_free_result(rUpdate);

						// create a directory with the package name
						sprintf(query, "mkdir \\\\tloyd-ser\\c$\\proj1618\\%s-%s", addm_group, addm_version);
						Exec proc(query);
						while(proc.getNext());	// wait for command to finish by reading the buffer till its empty
						ULONGLONG timestamp = GetTickCount64();
						sprintf(query, "mkdir \\\\tloyd-ser\\c$\\proj1618\\%s-%s\\%llu", addm_group, addm_version, timestamp);
						Exec proc5(query);
						while(proc5.getNext());	// wait for command to finish by reading the buffer till its empty

						// save off log file underneath the package name
						sprintf(query, "copy \\\\%s\\c$\\windows\\temp\\Msi*.log \\\\tloyd-ser\\c$\\proj1618\\%s-%s\\%llu\\ /y",
								tdata.get("netbios"), addm_group, addm_version, timestamp);
						printf("cmd = '%s'\n", query);
						system(query);

						// touch a file in order to save off the vm name
						sprintf(query, "cat > \\\\tloyd-ser\\c$\\proj1618\\_success\\%s-%s\\%llu\\_%s.txt",
								addm_group, addm_version, timestamp, tdata.get("netbios"));
						system(query);

						printf("copy complete\n");

						// Now we should abort this group install, once one fails we don't need to test the others
						ctr = loopmax;
						break;
					}
				}	
				// free up the result from the mysql query
				db->safe_mysql_free_result(rSelect);
			}
		}


		// free up the targetpc resource
		printf("Flagging targetpc as ready\n");
		//sprintf(query, "UPDATE n_msi_targetpc SET isAvailable=1, isReset=1 WHERE id=\"%s\"", tdata.get("targetpcId"));
		sprintf(query, "UPDATE n_msi_targetpc SET isAvailable=1 WHERE id=\"%s\"", tdata.get("targetpcId"));
		// thread-safe mysql call
		db->threadsafe_query(mutex, query);

		// update the track_id
		sprintf(query, "UPDATE n_msi_sequence_%s SET track_id=%s WHERE id=%s",
				detail.get("instance_id"), tdata.get("track_id"), sindex);
		// thread-safe mysql call
		rSelect = db->threadsafe_query(mutex, query);
		// free up the result from the mysql query
		db->safe_mysql_free_result(rSelect);


		// Clean up allocated memory
		OPENSSL_free(sindex);
		delete[] teststr;
		delete[] output;
	}

	// free up mysql thread-specific memory
	mysql_thread_end();
}

bool restoreVM(ParamManager *tdata, HANDLE mutex) {
	char query[DEFAULT_BUFLEN];
	char *next;

	// parse out passed in data
	char *targetpcId = tdata->get("targetpcId");
	char *netbios = tdata->get("netbios");
	char *vmIndex = tdata->get("vmIndex");
	char *snapshotId = tdata->get("snapshotId");

	// Restore VM from backup
	printf("Restoring VM %s\n", netbios);
	WaitForSingleObject(mutex, INFINITE);	// mutex protected command
	sprintf(query, "plink -i f:\\ssh\\pri.ppk 137.201.223.98 vim-cmd vmsvc/snapshot.revert %s %s 1",
		vmIndex, snapshotId);
	Exec proc(query);
	while(proc.getNext());	// wait for command to finish by reading the buffer till its empty
	ReleaseMutex(mutex);	// end mutext protection

	printf("Powering on VM %s\n", netbios);
	WaitForSingleObject(mutex, INFINITE);	// mutex protected command
	sprintf(query, "plink -i f:\\ssh\\pri.ppk 137.201.223.98 vim-cmd vmsvc/power.on %s", vmIndex);
	Exec proc2(query);
	while(next = proc2.getNext()) {
		if(!strcmp(next, "Power on failed")) {
			Sleep(20 * 1000);
			// try again
			printf("power on failed, retrying\n");
			Exec proc2(query);
		}
	}
	ReleaseMutex(mutex);	// end mutext protection

	// Now keep checking until the ADDM service is detected as running on the vm then wait 1 minute
	printf("Watching for VM %s to finish powering up\n", netbios);
	sprintf(query, "sc \\\\%s query addmSvc", netbios);
	int count = 0;
	bool isReady = false;
	while(!isReady) {
		Sleep(30 * 1000);
		printf("%s checking status of addmSvc\n", netbios);
		Exec proc(query);
		while(char *next = proc.getNext()) {
//			printf("%s sc output: '%s'\n", tdata->get("netbios"), next);
			if(strstr(next, "STATE              : 1  STOPPED")) {
				// service detected, start it up
				char sc_start[DEFAULT_BUFLEN];
				sprintf(sc_start, "sc \\\\%s start addmSvc", netbios);
				Exec proc4(sc_start);
				printf("%s addmSvc stopped, sending start command\n", netbios);

			}
			else if(strstr(next, "STATE              : 2  START_PENDING")) {
				// do nothing
			}
			else if(strstr(next, "STATE              : 3  STOP_PENDING")) {
				// do nothing, should never happen in our scenario anyway
			}
			else if(strstr(next, "STATE              : 4  RUNNING")) {
				// we aren't read until the addmSvc is running, and now it is
				isReady = true;
				break;
			}
		}

		// increase loop count & if 5 minutes have gone by assume the reboot is complete
		if((count ++) == 5 * 2) {
			// The RPC server is unavailable; Lost domain trust?  Give up.
			return false;
		}
	}

	return true;
}

// Using BIGNUMs convert a decimal of any size to the specified base
char* dec2base(char *_orig, char *_base, char *_length) {
	char *output = new char[atoi(_length) + 1];
	memset(output, 0, atoi(_length) + 1);
	int index = 0;

	BIGNUM *orig = BN_new();
	BIGNUM *base = BN_new();
	BN_CTX *ctx = BN_CTX_new();

	BN_dec2bn(&orig, _orig);
	BN_dec2bn(&base, _base);

	BIGNUM *result = BN_new();
	BIGNUM *rem = BN_new();

	// while orig > base
	BN_div(result, rem, orig, base, ctx);

	char *tmp = BN_bn2dec(rem);
//	printf("REM: %s", tmp);
	output[index ++]  = atoi(tmp);
	OPENSSL_free(tmp);

	while(!BN_is_zero(result)) {
		BN_div(result, rem, result, base, ctx);

		char *tmp = BN_bn2dec(rem);
//		printf(" %s", tmp);
		output[index ++]  = atoi(tmp);
		OPENSSL_free(tmp);
	}
//	printf("\n");

	BN_free(rem);
	BN_free(result);
	BN_CTX_free(ctx);
	BN_free(base);
	BN_free(orig);

	return output;
}


#ifdef __cplusplus
}
#endif	/*__cplusplus */